home *** CD-ROM | disk | FTP | other *** search
Wrap
Text File | 1995-05-14 | 60.6 KB | 1,446 lines
// copyright 1993 Michael B. Johnson; some portions copyright 1994, MIT // see COPYRIGHT for reuse legalities // // EveCommands have two important pieces of state in them: a single, atomic // eve command that they are based on, and a list of samples, representing // the various values of that eve command over the course of a scene. // They conform to the WWAnimatable protocol, and are probably the simplest // objects that conform to it. #import "EveCommand.h" #import "TCLCommand.h" #import "WWTCLKit.h" //#import "WWTCLClosedCmd.h" #import "WWEveParser.h" #import "Protocol_WWSample.h" #import "WWSample.h" #import "WWSampleList.h" #import "WW3DShape.h" #import "WW3DWell.h" // has the sceneClock protocol - formalize this! @implementation EveCommand + initialize { return [EveCommand setVersion:2], self; } static char *WriteProc(ClientData clientData, Tcl_Interp *interp, char *name1, char *name2, int flags) { id eveCmd; if ([(id)clientData respondsTo:@selector(datum)]) { eveCmd = [(id)clientData datum]; if ([eveCmd conformsTo:@protocol(WWAnimatable)]) { [eveCmd resample]; } else { NXLogError("%s doesn't conform to the WWAnimatable protocol - ", [eveCmd name]); NXLogError("why was it stored as the datum of a WWTCLVarTrace and sent to an EveCommand's WriteProc?\n"); } } else { NXLogError("%s isn't a WWTCLVarTrace - why is it getting sent to an EveCommand's WriteProc?\n", [(id)clientData name]); } return NULL; } - initWithInterp:newInterp eveParser:newEveParser shape:newShape cmd:(char *)newCmd clock:newSceneClock frozen:(BOOL)frozenOrMalleable { char *ptr, *firstCmd = NULL, *cmdString; id <WWSample> newSample; WWTCLClosedCmd *tmpCmd, *closedCmd; [super init]; isTCLCommand = NO; interp = newInterp; eveParser = newEveParser; myShape = newShape; // we'll use this temporarily... tmpCmd = [[WWTCLClosedCmd alloc] initWithInterp:interp]; [tmpCmd setOriginalExpression:newCmd]; cmd = tmpCmd; sceneClock = newSceneClock; samplesList = [[WWSampleList alloc] initCount:1]; // so we don't need to grow for the default case // the following might not just be a simple subclass of RIBCommand, // but rather a tcl command or an articulated procedure. if it's an // articulatable tcl command like "set", we need to catch that ptr = cmdString = (char *)[tmpCmd cmd]; // "set yabba" would give a ptr of 3 while (*ptr && (*ptr != ' ')) { ptr++; } firstCmd = (char *)calloc(1, (1 + (ptr - cmdString))); strncpy(firstCmd, cmdString, (ptr - cmdString)); if (!strcmp(firstCmd, "set")) { // the articulated command is the tcl command "set" // we need to malloc up a TCLCommand object and we're done // WAVE: Need to deal with sampling tcl variables over time... // need to grab this value and save it as a sample, right? if (!frozenOrMalleable) { [eveParser evaluateEveCommand:(char *)[cmd cmd]]; // go ahead and do the set now... newSample = [[[WWSample alloc] init] setData:[[[TCLCommand alloc] init] setCmd:[tmpCmd cmd]] timestamp:[sceneClock timestamp] generator:[eveParser currentSampleGeneratorName] weight:[eveParser currentSampleWeight]]; [samplesList addSample:newSample]; } isTCLCommand = YES; } else { // okay, it's a regular old RIBCommand (I hope...) // we need to evaluate the eve command at least once to get initial info from it. if (!frozenOrMalleable) { firstSampleData = [self produceSampleFromCmd]; if (!firstSampleData) { return nil; } newSample = [[[WWSample alloc] init] setData:firstSampleData timestamp:[sceneClock timestamp] generator:[eveParser currentSampleGeneratorName] weight:[eveParser currentSampleWeight]]; [samplesList addSample:newSample]; } else { firstSampleData = nil; } } // need to parse cmd to get the names of all the tcl variables referenced - only if malleable if (!frozenOrMalleable) { closedCmd = [[eveParser tclInterp] generateClosureFrom:(char *)[cmd cmd] andForGlobalsCall:(Tcl_VarTraceProc *)WriteProc usingData:(ClientData)self]; //NXLogError("cmd:\n\t<%s>\nbecame closedCmd:\n\t<%s>\n", [cmd cmd], [closedCmd cmd]); cmd = closedCmd; [tmpCmd free]; // we're done with this; lose it } else { // we're gonna hold on to the tmpCmd; } // since an EveCommand also might be a "set" command, which wouldn't have a firstSampleData, we do this... dirtyBoundingBox = YES; hasBoundingBox = NO; isMotionBlurrable = NO; isCompoundCommand = NO; if (firstSampleData) { hasBoundingBox = [firstSampleData hasBoundingBox]; isMotionBlurrable = [firstSampleData isMotionBlurrable]; isCompoundCommand = [firstSampleData isCompoundCommand]; pushesCTM = [firstSampleData pushesCTM]; popsCTM = [firstSampleData popsCTM]; } if (firstCmd) { int firstCmdLen = strlen(firstCmd); sampleName = (char *)malloc(firstCmdLen + 3); sampleName[0] = '('; strcpy((sampleName + 1), firstCmd); sampleName[firstCmdLen + 1] = ')'; sampleName[firstCmdLen + 2] = '\0'; free(firstCmd); } cacheIsFilled = NO; return self; } - copyFromZone:(NXZone *)zone { return [super copyFromZone:zone]; } - (BOOL)isLerpable { return NO; } - lerpWith:b by:(float)uValue { return self; } - lerpSelfWith:b by:(float)uValue { return self; } - (BOOL)pushesCTM { return pushesCTM; } - (BOOL)popsCTM { return popsCTM; } - (BOOL)pushesOrPopsCTM { return (popsCTM || pushesCTM); } - awake { [super awake]; if (!eveParser) { NXLogError("warning!: the EveCmd <%s> has been disconnected from its eveParser.\n", cmd); return nil; } if (!myShape) { NXLogError("warning!: the EveCmd <%s> has been disconnected from its shape.\n", cmd); return nil; } firstSampleData = [samplesList objectAt:0]; cacheIsFilled = NO; dirtyBoundingBox = YES; return self; } - free { //NXLogError("%s %p being free'ed - freeing cmd %p, samplesList %p and its objects\n", // [[self class] name], self, cmd, samplesList); [cmd free]; // this takes care of the traces [[samplesList freeObjects] free]; return [super free]; } - evalAndAddAsSamples:(char *)newSamplesList { int argc, argc2, argc3, argc4, i, j; char **argv = NULL, **argv2 = NULL, **argv3 = NULL, **argv4 = NULL; id <WWSample> newSample = nil, sampleData = nil; WWTCLClosedCmd *theCmd; float currentTimeStamp; // we're gonna muck with the cache; don't let it think it's clean cacheIsFilled = NO; // we want to hold on to cmd... theCmd = cmd; Tcl_SplitList([interp interp], newSamplesList, &argc, &argv); // argc == how many timestamps // okay, we've now split it into the list of time samples // for each time sample, we want to split the list into component samples for (i = 0; i < argc; i++) // for each timestamp { Tcl_SplitList([interp interp], argv[i], &argc2, &argv2); // argc2 == 2 if (argc2 != 2) { NXLogError("sample #%d of cmd <%s> was badly formed: %d arguments instead of two!\n", i, [cmd cmd], argc2); if (argv) { free(argv); } if (argv2) { free(argv2); } cmd = theCmd; return nil; } // okay, we've now got a time sample // argv2[0] == timestamp // argv2[1] == list of sample components currentTimeStamp = (float)atof(argv2[0]); Tcl_SplitList([interp interp], argv2[1], &argc3, &argv3); // argc3 == number of sample components for (j = 0; j < argc3; j++) { Tcl_SplitList([interp interp], argv3[j], &argc4, &argv4); // argc4 == 3 if (argc4 != 3) { NXLogError("sample component #%d of sample #%d of cmd <%s> was badly formed: %d arguments instead of three!\n", j, i, [cmd cmd], argc4); if (argv) { free(argv); } if (argv2) { free(argv2); } if (argv3) { free(argv3); } if (argv4) { free(argv4); } cmd = theCmd; return nil; } // okay, we've now got a list of named components that comprise this sample // argv4[0] == generatorName // argv4[1] == weight // argv4[2] == compiled cmd cmd = [[WWTCLClosedCmd alloc] initWithInterp:interp]; [cmd setOriginalExpression:argv4[2]]; sampleData = [self produceSampleFromCmd]; if (!sampleData) { NXLogError("warning: the animatable expression <%s> was not able to ", [cmd cmd]); NXLogError("generate a valid renderable object for itself when rebuilding from a sample list\n"); if (argv) { free(argv); } if (argv2) { free(argv2); } if (argv3) { free(argv3); } if (argv4) { free(argv4); } [cmd free]; cmd = theCmd; return nil; } newSample = [[[WWSample alloc] init] setData:sampleData timestamp:currentTimeStamp generator:argv4[0] weight:(float)atof(argv4[1])]; [samplesList addSample:newSample]; } if (argv2) { free(argv2); argv2 = NULL; } if (argv3) { free(argv3); argv3 = NULL; } if (argv4) { free(argv4); argv4 = NULL; } } if (argv) { free(argv); } firstSampleData = [newSample data]; if (firstSampleData) { hasBoundingBox = [firstSampleData hasBoundingBox]; isMotionBlurrable = [firstSampleData isMotionBlurrable]; isCompoundCommand = [firstSampleData isCompoundCommand]; pushesCTM = [firstSampleData pushesCTM]; popsCTM = [firstSampleData popsCTM]; } cacheIsFilled = NO; // restore cmd [cmd free]; cmd = theCmd; return self; } - produceSampleFromCmd { return [eveParser evaluateEveCommand:(char *)[(WWTCLClosedCmd *)cmd cmd]]; } - (float)lastSampleIsAt { return [samplesList lastSampleIsAt]; } - (unsigned long int)maxSampleBandwidth { return [samplesList maxSampleBandwidth]; } - resample { id <WWSample> newSample; id renderableCommand; float timestamp; if (isTCLCommand) { [eveParser evaluateEveCommand:(char *)[cmd cmd]]; newSample = [[[WWSample alloc] init] setData:[[[TCLCommand alloc] init] setCmd:[cmd cmd]] timestamp:[sceneClock timestamp] generator:[eveParser currentSampleGeneratorName] weight:[eveParser currentSampleWeight]]; [samplesList addSample:newSample]; } else { // the old one is already in the list... renderableCommand = [self produceSampleFromCmd]; if (!renderableCommand) { NXLogError("warning: the %s <%s> wasn't able to generate a valid renderable object for itself when resampling\n", [self name], [cmd cmd]); return nil; } newSample = [[[WWSample alloc] init] setData:renderableCommand timestamp:[sceneClock timestamp] generator:[eveParser currentSampleGeneratorName] weight:[eveParser currentSampleWeight]]; [samplesList addSample:newSample]; } // for now, we'll invalidate my cache and stick this new data sample into it (so boundingBox works) cacheIsFilled = NO; firstSampleData = [newSample data]; // if the new sample is between bbIntervalStart and bbIntervalEnd, invalidate the boundingBox cache timestamp = [newSample timestamp]; if ((timestamp >= bbIntervalStart) && (timestamp <= bbIntervalEnd)) { dirtyBoundingBox = YES; [myShape setBoundingBoxDirty]; } return self; } - setMyShape:shape { myShape = shape; return self; } - shape { return myShape; } - (BOOL)hasBoundingBox { return hasBoundingBox; } - setBoundingBox:(RtBound *)newBoundingBox { N3D_CopyBound(*newBoundingBox, boundingBox); return self; } // pre-rendering is used for generating shadows and stuff... // for right now, we don't do any performance hacks. - preRenderSelf:(WW3DCamera *)camera startingAt:(RtFloat)shutterOpenTime endingAt:(RtFloat)shutterCloseTime { return [self renderSelf:camera startingAt:shutterOpenTime endingAt:shutterCloseTime]; } - preRenderSelf:(WW3DCamera *)camera { return [self renderSelf:camera]; } - renderCompoundCommandMaps:theFirstSampleData :theLastSampleData to:(WW3DCamera *)camera startingAt:(RtFloat)shutterOpenTime endingAt:(RtFloat)shutterCloseTime usingStream:(NXStream *)ns { int howMany1 = [theFirstSampleData count]; int howMany2 = [theLastSampleData count]; RtFloat bogusTimeVector[2]; int i; id <WWRenderable> data1, data2; if (howMany1 == howMany2) // it looks like these might be worth trying to motion blur together... { for (i = 0; i < howMany1; i++) { // check to see if the two commands are actually of the same type. If // they are, check to see if they're motion blurrable. If they are, // motion blur them. data1 = [theFirstSampleData objectAt:i]; // cache the first data a bit data2 = [theLastSampleData objectAt:i]; // cache the last data a bit if ([data1 class] == [data2 class]) { // okay, they're the same kind of command if ([data1 isMotionBlurrable]) { if ([data1 isCompoundCommand]) // one last check; do we go recursive? { [self renderCompoundCommandMaps:data1 :data2 to:camera startingAt:shutterOpenTime endingAt:shutterCloseTime usingStream:ns]; } else // okay, it's atomic so try to motion blur it { // actually, I should ask the first command to // compare itself to the second, and only motion // blur it if it thinks they're different. // also, I could check at this point if the cmd actually does anything (i.e. a Translate of 0 0 0, etc.) // should actually check the length of the list and construct an appropriate vector, // but prman 3.4 has the restriction that you can only use two samples, sigh... bogusTimeVector[0] = shutterOpenTime; bogusTimeVector[1] = shutterCloseTime; if ([data1 theSameAs:data2]) // no need for motion blur; t'aint changing... { if (![data1 isMoot]) // only render it if it will actually *do* something... { [data1 renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime usingStream:ns]; } } else { [data1 renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime usingStream:ns]; [data2 renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime usingStream:ns]; } } } else // it's not motion blurrable; just use the first command { [data1 renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime usingStream:ns]; } } else // the two commands are different; only render the first representation { { [data1 renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime usingStream:ns]; } } } } else // it's not worth trying, just render the commands in the first sample { for (i = 0; i < howMany1; i++) { [[theFirstSampleData objectAt:i] renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime usingStream:ns]; } } return self; } - renderMaps:(WW3DCamera *)camera startingAt:(RtFloat)shutterOpenTime endingAt:(RtFloat)shutterCloseTime usingStream:(NXStream *)ns { RtFloat bogusTimeVector[2]; if (cacheIsFilled && (shutterOpenTime == lastShutterOpenTime) && (shutterCloseTime == lastShutterCloseTime)) { // use my cached representation!! // first: is the renderable command I represent even motion-blurrable? // second: are we using prman or qrman? Don't waste this on qrman, it'll just get mad... if (isMotionBlurrable && (NXDrawingStatus != NX_DRAWING)) { if (shutterOpenTime == shutterCloseTime) // doesn't matter; it's a strobe shot { // use first cached renderable command to be Renderman compliant [(id <WWRenderable>)firstSampleData renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime usingStream:ns]; } else // same shot, but need to do it motion blurred! { // should actually check the length of the list and construct an appropriate vector, // but prman 3.4 has the restriction that you can only use two samples, sigh... bogusTimeVector[0] = shutterOpenTime; bogusTimeVector[1] = shutterCloseTime; if (isCompoundCommand) // it's a compound command; need to walk down the two { [self renderCompoundCommandMaps:firstSampleData :lastSampleData to:camera startingAt:shutterOpenTime endingAt:shutterCloseTime usingStream:ns]; } else // it's an atomic RIBCommand... { // actually, I should ask the first command to // compare itself to the second, and only motion // blur it if it thinks they're different. // also, I could check at this point if ... what? I forgot... if ([firstSampleData theSameAs:lastSampleData]) // no need for motion blur; t'aint changing... { [firstSampleData renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime usingStream:ns]; } else { [firstSampleData renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime usingStream:ns]; [lastSampleData renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime usingStream:ns]; } } } } else { // either we're going to the screen or this command isn't even motion-blurrable... [firstSampleData renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime usingStream:ns]; } } else { // okay, we're being asked to render a different (set of) sample(s) of myself // we'll refill the cache and then render // we now need to find the sample in the samplesList that had a // timestamp <= shutterOpenTime, where the next sample past it is < // shutterOpenTime, and then we need to find the sample that's // timestamp is <= shutterCloseTime where the next sample past it is > // shutterCloseTime. // probably the best thing to do is have samples be stored in a subclass of List // that caches the last answers to questions and searches itself accordingly... // In this case, it's a WWSampleList, which is built to store WWSamples firstSampleData = [(WWSample *)[samplesList sampleAtOrJustBefore:shutterOpenTime] data]; lastSampleData = [(WWSample *)[samplesList sampleAtOrJustBefore:shutterCloseTime] data]; if (!firstSampleData) { if (!lastSampleData) { NXLogError("couldn't get any valid samples for <%s> for times %f through %f\n", [cmd cmd], shutterOpenTime, shutterCloseTime); return nil; } else { NXLogError("couldn't get a valid sample for <%s> at open time %f - using sample at %f...\n", [cmd cmd], shutterOpenTime, shutterCloseTime); firstSampleData = lastSampleData; } } else { if (!lastSampleData) { NXLogError("couldn't get a valid sample for <%s> at close time %f - using sample at %f...\n", [(WWTCLClosedCmd *)cmd cmd], shutterCloseTime, shutterOpenTime); lastSampleData = firstSampleData; } } lastShutterOpenTime = shutterOpenTime; lastShutterCloseTime = shutterCloseTime; isMotionBlurrable = [firstSampleData isMotionBlurrable]; isCompoundCommand = [firstSampleData isCompoundCommand]; cacheIsFilled = YES; if (isMotionBlurrable && (NXDrawingStatus != NX_DRAWING)) { if (shutterOpenTime == shutterCloseTime) // doesn't matter; it's a strobe shot { // use first cached renderable command to be Renderman compliant [firstSampleData renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime usingStream:ns]; } else // same shot, but need to do it motion blurred! { if (firstSampleData && lastSampleData) { bogusTimeVector[0] = shutterOpenTime; bogusTimeVector[1] = shutterCloseTime; if (isCompoundCommand) // it's a compound command; need to walk down the two { [self renderCompoundCommandMaps:firstSampleData :lastSampleData to:camera startingAt:shutterOpenTime endingAt:shutterCloseTime usingStream:ns]; } else // it's an atomic RIBCommand... { // actually, I should ask the first command to // compare itself to the second, and only motion // blur it if it thinks they're different. // also, I could check at this point if ... what? I forgot... if ([firstSampleData theSameAs:lastSampleData]) // no need for motion blur; t'aint changing... { [firstSampleData renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime usingStream:ns]; } else { [firstSampleData renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime usingStream:ns]; [lastSampleData renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime usingStream:ns]; } } } if (!firstSampleData) { NXLogError("couldn't get a valid first sample for <%s>\n", [(WWTCLClosedCmd *)cmd cmd]); if (lastSampleData) { [lastSampleData renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime usingStream:ns]; } } if (!lastSampleData) { NXLogError("couldn't get a valid first sample for <%s>\n", [(WWTCLClosedCmd *)cmd cmd]); if (firstSampleData) { [firstSampleData renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime usingStream:ns]; } } } // closes "else // same shot, but need to do it motion blurred!" } else { // either we're going to the screen or this command isn't even motion-blurrable... [firstSampleData renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime usingStream:ns]; } } return self; } - renderMaps:(WW3DCamera *)camera usingStream:(NXStream *)ns { RtFloat shutterOpenTime = [camera shutterOpenTime], shutterCloseTime = [camera shutterCloseTime]; return [self renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime usingStream:ns]; } - renderCompoundCommandMaps:theFirstSampleData :theLastSampleData to:(WW3DCamera *)camera startingAt:(RtFloat)shutterOpenTime endingAt:(RtFloat)shutterCloseTime { int howMany1 = [theFirstSampleData count]; int howMany2 = [theLastSampleData count]; RtFloat bogusTimeVector[2]; int i; id <WWRenderable> data1, data2; if (howMany1 == howMany2) // it looks like these might be worth trying to motion blur together... { for (i = 0; i < howMany1; i++) { // check to see if the two commands are actually of the same type. If // they are, check to see if they're motion blurrable. If they are, // motion blur them. data1 = [theFirstSampleData objectAt:i]; // cache the first data a bit data2 = [theLastSampleData objectAt:i]; // cache the last data a bit if ([data1 class] == [data2 class]) { // okay, they're the same kind of command if ([data1 isMotionBlurrable]) { if ([data1 isCompoundCommand]) // one last check; do we go recursive? { [self renderCompoundCommandMaps:data1 :data2 to:camera startingAt:shutterOpenTime endingAt:shutterCloseTime]; } else // okay, it's atomic so try to motion blur it { // actually, I should ask the first command to // compare itself to the second, and only motion // blur it if it thinks they're different. // also, I could check at this point if the cmd actually does anything (i.e. a Translate of 0 0 0, etc.) // should actually check the length of the list and construct an appropriate vector, // but prman 3.4 has the restriction that you can only use two samples, sigh... bogusTimeVector[0] = shutterOpenTime; bogusTimeVector[1] = shutterCloseTime; if ([data1 theSameAs:data2]) // no need for motion blur; t'aint changing... { if (![data1 isMoot]) // only render it if it will actually *do* something... { [data1 renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime]; } } else { [data1 renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime]; [data2 renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime]; } } } else // it's not motion blurrable; just use the first command { [data1 renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime]; } } else // the two commands are different; only render the first representation { { [data1 renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime]; } } } } else // it's not worth trying, just render the commands in the first sample { for (i = 0; i < howMany1; i++) { [[theFirstSampleData objectAt:i] renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime]; } } return self; } - renderMaps:(WW3DCamera *)camera startingAt:(RtFloat)shutterOpenTime endingAt:(RtFloat)shutterCloseTime { RtFloat bogusTimeVector[2]; if (cacheIsFilled && (shutterOpenTime == lastShutterOpenTime) && (shutterCloseTime == lastShutterCloseTime)) { // use my cached representation!! // first: is the renderable command I represent even motion-blurrable? // second: are we using prman or qrman? Don't waste this on qrman, it'll just get mad... if (isMotionBlurrable && (NXDrawingStatus != NX_DRAWING)) { if (shutterOpenTime == shutterCloseTime) // doesn't matter; it's a strobe shot { // use first cached renderable command to be Renderman compliant [(id <WWRenderable>)firstSampleData renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime]; } else // same shot, but need to do it motion blurred! { // should actually check the length of the list and construct an appropriate vector, // but prman 3.4 has the restriction that you can only use two samples, sigh... bogusTimeVector[0] = shutterOpenTime; bogusTimeVector[1] = shutterCloseTime; if (isCompoundCommand) // it's a compound command; need to walk down the two { [self renderCompoundCommandMaps:firstSampleData :lastSampleData to:camera startingAt:shutterOpenTime endingAt:shutterCloseTime]; } else // it's an atomic RIBCommand... { // actually, I should ask the first command to // compare itself to the second, and only motion // blur it if it thinks they're different. // also, I could check at this point if ... what? I forgot... if ([firstSampleData theSameAs:lastSampleData]) // no need for motion blur; t'aint changing... { if (![firstSampleData isMoot]) // only render it if it will actually *do* something... { [firstSampleData renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime]; } } else { [firstSampleData renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime]; [lastSampleData renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime]; } } } } else { // either we're going to the screen or this command isn't even motion-blurrable... [firstSampleData renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime]; } } else { // okay, we're being asked to render a different (set of) sample(s) of myself // we'll refill the cache and then render // we now need to find the sample in the samplesList that had a // timestamp <= shutterOpenTime, where the next sample past it is < // shutterOpenTime, and then we need to find the sample that's // timestamp is <= shutterCloseTime where the next sample past it is > // shutterCloseTime. // probably the best thing to do is have samples be stored in a subclass of List // that caches the last answers to questions and searches itself accordingly... // In this case, it's a WWSampleList, which is built to store WWSamples firstSampleData = [(WWSample *)[samplesList sampleAtOrJustBefore:shutterOpenTime] data]; lastSampleData = [(WWSample *)[samplesList sampleAtOrJustBefore:shutterCloseTime] data]; if (!firstSampleData) { if (!lastSampleData) { NXLogError("couldn't get any valid samples for <%s> for times %f through %f\n", [cmd cmd], shutterOpenTime, shutterCloseTime); return nil; } else { NXLogError("couldn't get a valid sample for <%s> at open time %f - using sample at %f...\n", [cmd cmd], shutterOpenTime, shutterCloseTime); firstSampleData = lastSampleData; } } else { if (!lastSampleData) { NXLogError("couldn't get a valid sample for <%s> at close time %f - using sample at %f...\n", [(WWTCLClosedCmd *)cmd cmd], shutterCloseTime, shutterOpenTime); lastSampleData = firstSampleData; } } lastShutterOpenTime = shutterOpenTime; lastShutterCloseTime = shutterCloseTime; isMotionBlurrable = [firstSampleData isMotionBlurrable]; isCompoundCommand = [firstSampleData isCompoundCommand]; cacheIsFilled = YES; if (isMotionBlurrable && (NXDrawingStatus != NX_DRAWING)) { if (shutterOpenTime == shutterCloseTime) // doesn't matter; it's a strobe shot { // use first cached renderable command to be Renderman compliant [firstSampleData renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime]; } else // same shot, but need to do it motion blurred! { if (firstSampleData && lastSampleData) { bogusTimeVector[0] = shutterOpenTime; bogusTimeVector[1] = shutterCloseTime; if (isCompoundCommand) // it's a compound command; need to walk down the two { [self renderCompoundCommandMaps:firstSampleData :lastSampleData to:camera startingAt:shutterOpenTime endingAt:shutterCloseTime]; } else // it's an atomic RIBCommand... { // actually, I should ask the first command to // compare itself to the second, and only motion // blur it if it thinks they're different. // also, I could check at this point if ... what? I forgot... if ([firstSampleData theSameAs:lastSampleData]) // no need for motion blur; t'aint changing... { if (![firstSampleData isMoot]) // only render it if it will actually *do* something... { [firstSampleData renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime]; } } else { [firstSampleData renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime]; [lastSampleData renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime]; } } } if (!firstSampleData) { NXLogError("couldn't get a valid first sample for <%s>\n", [(WWTCLClosedCmd *)cmd cmd]); if (lastSampleData) { [lastSampleData renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime]; } } if (!lastSampleData) { NXLogError("couldn't get a valid first sample for <%s>\n", [(WWTCLClosedCmd *)cmd cmd]); if (firstSampleData) { [firstSampleData renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime]; } } } // closes "else // same shot, but need to do it motion blurred!" } else { // either we're going to the screen or this command isn't even motion-blurrable... [firstSampleData renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime]; } } return self; } - renderMaps:(WW3DCamera *)camera { RtFloat shutterOpenTime = [camera shutterOpenTime], shutterCloseTime = [camera shutterCloseTime]; return [self renderMaps:camera startingAt:shutterOpenTime endingAt:shutterCloseTime]; } - renderCompoundCommand:theFirstSampleData :theLastSampleData to:(WW3DCamera *)camera startingAt:(RtFloat)shutterOpenTime endingAt:(RtFloat)shutterCloseTime { int howMany1 = [theFirstSampleData count]; int howMany2 = [theLastSampleData count]; RtFloat bogusTimeVector[2]; int i; id <WWRenderable> data1, data2; if (howMany1 == howMany2) // it looks like these might be worth trying to motion blur together... { for (i = 0; i < howMany1; i++) { // check to see if the two commands are actually of the same type. If // they are, check to see if they're motion blurrable. If they are, // motion blur them. data1 = [theFirstSampleData objectAt:i]; // cache the first data a bit data2 = [theLastSampleData objectAt:i]; // cache the last data a bit if ([data1 class] == [data2 class]) { // okay, they're the same kind of command if ([data1 isMotionBlurrable]) { if ([data1 isCompoundCommand]) // one last check; do we go recursive? { [self renderCompoundCommand:data1 :data2 to:camera startingAt:shutterOpenTime endingAt:shutterCloseTime]; } else // okay, it's atomic so try to motion blur it { // actually, I should ask the first command to // compare itself to the second, and only motion // blur it if it thinks they're different. // also, I could check at this point if the cmd actually does anything (i.e. a Translate of 0 0 0, etc.) // should actually check the length of the list and construct an appropriate vector, // but prman 3.4 has the restriction that you can only use two samples, sigh... bogusTimeVector[0] = shutterOpenTime; bogusTimeVector[1] = shutterCloseTime; if ([data1 theSameAs:data2] || (![data1 similarTo:data2])) // no need for motion blur; t'aint changing or is too weird... { if (![data1 isMoot]) // only render it if it will actually *do* something... { [data1 renderSelf:camera startingAt:shutterOpenTime endingAt:shutterCloseTime]; } } else { RiMotionBeginV((RtInt)2, bogusTimeVector); [data1 renderSelf:camera startingAt:shutterOpenTime endingAt:shutterCloseTime]; [data2 renderSelf:camera startingAt:shutterOpenTime endingAt:shutterCloseTime]; RiMotionEnd(); } } } else // it's not motion blurrable; just use the first command { if (![data1 isMoot]) // only render it if it will actually *do* something... { [data1 renderSelf:camera startingAt:shutterOpenTime endingAt:shutterCloseTime]; } } } else // the two commands are different; only render the first representation { if (![data1 isMoot]) // only render it if it will actually *do* something... { [data1 renderSelf:camera startingAt:shutterOpenTime endingAt:shutterCloseTime]; } } } } else // it's not worth trying, just render the commands in the first sample { for (i = 0; i < howMany1; i++) { [[theFirstSampleData objectAt:i] renderSelf:camera startingAt:shutterOpenTime endingAt:shutterCloseTime]; } } return self; } - renderSelfAsBox:(WW3DCamera *)camera startingAt:(RtFloat)shutterOpenTime endingAt:(RtFloat)shutterCloseTime { RtFloat bogusTimeVector[2]; if (cacheIsFilled && (shutterOpenTime == lastShutterOpenTime) && (shutterCloseTime == lastShutterCloseTime)) { // use my cached representation!! // first: is the renderable command I represent even motion-blurrable? // second: are we using prman or qrman? Don't waste this on qrman, it'll just get mad... if (isMotionBlurrable && (NXDrawingStatus != NX_DRAWING)) { if (shutterOpenTime == shutterCloseTime) // doesn't matter; it's a strobe shot { // use first cached renderable command to be Renderman compliant [(id <WWRenderable>)firstSampleData renderSelfAsBox:camera startingAt:shutterOpenTime endingAt:shutterCloseTime]; } else // same shot, but need to do it motion blurred! { // should actually check the length of the list and construct an appropriate vector, // but prman 3.4 has the restriction that you can only use two samples, sigh... bogusTimeVector[0] = shutterOpenTime; bogusTimeVector[1] = shutterCloseTime; if (isCompoundCommand) // it's a compound command; need to walk down the two { [self renderCompoundCommand:firstSampleData :lastSampleData to:camera startingAt:shutterOpenTime endingAt:shutterCloseTime]; } else // it's an atomic RIBCommand... { // actually, I should ask the first command to // compare itself to the second, and only motion // blur it if it thinks they're different. // also, I could check at this point if ... what? I forgot... if ([firstSampleData theSameAs:lastSampleData] || (![firstSampleData similarTo:lastSampleData])) { // no need for motion blur; t'aint changing or is too weird... if (![firstSampleData isMoot]) // only render it if it will actually *do* something... { [firstSampleData renderSelfAsBox:camera startingAt:shutterOpenTime endingAt:shutterCloseTime]; } } else { RiMotionBeginV((RtInt)2, bogusTimeVector); [firstSampleData renderSelfAsBox:camera startingAt:shutterOpenTime endingAt:shutterCloseTime]; [lastSampleData renderSelfAsBox:camera startingAt:shutterOpenTime endingAt:shutterCloseTime]; RiMotionEnd(); } } } } else { // either we're going to the screen or this command isn't even motion-blurrable... [firstSampleData renderSelfAsBox:camera startingAt:shutterOpenTime endingAt:shutterCloseTime]; } } else { // okay, we're being asked to render a different (set of) sample(s) of myself // we'll refill the cache and then render // we now need to find the sample in the samplesList that had a // timestamp <= shutterOpenTime, where the next sample past it is < // shutterOpenTime, and then we need to find the sample that's // timestamp is <= shutterCloseTime where the next sample past it is > // shutterCloseTime. // probably the best thing to do is have samples be stored in a subclass of List // that caches the last answers to questions and searches itself accordingly... // In this case, it's a WWSampleList, which is built to store WWSamples firstSampleData = [(WWSample *)[samplesList sampleAtOrJustBefore:shutterOpenTime] data]; lastSampleData = [(WWSample *)[samplesList sampleAtOrJustBefore:shutterCloseTime] data]; if (!firstSampleData) { if (!lastSampleData) { NXLogError("couldn't get any valid samples for <%s> for times %f through %f\n", [cmd cmd], shutterOpenTime, shutterCloseTime); return nil; } else { NXLogError("couldn't get a valid sample for <%s> at open time %f - using sample at %f...\n", [cmd cmd], shutterOpenTime, shutterCloseTime); firstSampleData = lastSampleData; } } else { if (!lastSampleData) { NXLogError("couldn't get a valid sample for <%s> at close time %f - using sample at %f...\n", [(WWTCLClosedCmd *)cmd cmd], shutterCloseTime, shutterOpenTime); lastSampleData = firstSampleData; } } lastShutterOpenTime = shutterOpenTime; lastShutterCloseTime = shutterCloseTime; isMotionBlurrable = [firstSampleData isMotionBlurrable]; isCompoundCommand = [firstSampleData isCompoundCommand]; cacheIsFilled = YES; if (isMotionBlurrable && (NXDrawingStatus != NX_DRAWING)) { if (shutterOpenTime == shutterCloseTime) // doesn't matter; it's a strobe shot { // use first cached renderable command to be Renderman compliant [firstSampleData renderSelfAsBox:camera startingAt:shutterOpenTime endingAt:shutterCloseTime]; } else // same shot, but need to do it motion blurred! { if (firstSampleData && lastSampleData) { bogusTimeVector[0] = shutterOpenTime; bogusTimeVector[1] = shutterCloseTime; if (isCompoundCommand) // it's a compound command; need to walk down the two { [self renderCompoundCommand:firstSampleData :lastSampleData to:camera startingAt:shutterOpenTime endingAt:shutterCloseTime]; } else // it's an atomic RIBCommand... { // actually, I should ask the first command to // compare itself to the second, and only motion // blur it if it thinks they're different. // also, I could check at this point if ... what? I forgot... if ([firstSampleData theSameAs:lastSampleData] || (![firstSampleData similarTo:lastSampleData])) { // no need for motion blur; t'aint changing or is too weird... if (![firstSampleData isMoot]) // only render it if it will actually *do* something... { [firstSampleData renderSelfAsBox:camera startingAt:shutterOpenTime endingAt:shutterCloseTime]; } } else { RiMotionBeginV((RtInt)2, bogusTimeVector); [firstSampleData renderSelfAsBox:camera startingAt:shutterOpenTime endingAt:shutterCloseTime]; [lastSampleData renderSelfAsBox:camera startingAt:shutterOpenTime endingAt:shutterCloseTime]; RiMotionEnd(); } } } if (!firstSampleData) { NXLogError("couldn't get a valid first sample for <%s>\n", [(WWTCLClosedCmd *)cmd cmd]); if (lastSampleData) { [lastSampleData renderSelfAsBox:camera startingAt:shutterOpenTime endingAt:shutterCloseTime]; } } if (!lastSampleData) { NXLogError("couldn't get a valid first sample for <%s>\n", [(WWTCLClosedCmd *)cmd cmd]); if (firstSampleData) { [firstSampleData renderSelfAsBox:camera startingAt:shutterOpenTime endingAt:shutterCloseTime]; } } } // closes "else // same shot, but need to do it motion blurred!" } else { // either we're going to the screen or this command isn't even motion-blurrable... [firstSampleData renderSelfAsBox:camera startingAt:shutterOpenTime endingAt:shutterCloseTime]; } } return self; } - renderSelf:(WW3DCamera *)camera startingAt:(RtFloat)shutterOpenTime endingAt:(RtFloat)shutterCloseTime { RtFloat bogusTimeVector[2]; if (cacheIsFilled && (shutterOpenTime == lastShutterOpenTime) && (shutterCloseTime == lastShutterCloseTime)) { // use my cached representation!! // first: is the renderable command I represent even motion-blurrable? // second: are we using prman or qrman? Don't waste this on qrman, it'll just get mad... if (isMotionBlurrable && (NXDrawingStatus != NX_DRAWING)) { if (shutterOpenTime == shutterCloseTime) // doesn't matter; it's a strobe shot { // use first cached renderable command to be Renderman compliant if (![firstSampleData isMoot]) // only render it if it will actually *do* something... { [(id <WWRenderable>)firstSampleData renderSelf:camera startingAt:shutterOpenTime endingAt:shutterCloseTime]; } } else // same shot, but need to do it motion blurred! { // should actually check the length of the list and construct an appropriate vector, // but prman 3.4 has the restriction that you can only use two samples, sigh... bogusTimeVector[0] = shutterOpenTime; bogusTimeVector[1] = shutterCloseTime; if (isCompoundCommand) // it's a compound command; need to walk down the two { [self renderCompoundCommand:firstSampleData :lastSampleData to:camera startingAt:shutterOpenTime endingAt:shutterCloseTime]; } else // it's an atomic RIBCommand... { // actually, I should ask the first command to // compare itself to the second, and only motion // blur it if it thinks they're different. // also, I could check at this point if ... what? I forgot... if ([firstSampleData theSameAs:lastSampleData] || (![firstSampleData similarTo:lastSampleData])) { // no need for motion blur; t'aint changing or is too weird... if (![firstSampleData isMoot]) // only render it if it will actually *do* something... { [firstSampleData renderSelf:camera startingAt:shutterOpenTime endingAt:shutterCloseTime]; } } else { RiMotionBeginV((RtInt)2, bogusTimeVector); [firstSampleData renderSelf:camera startingAt:shutterOpenTime endingAt:shutterCloseTime]; [lastSampleData renderSelf:camera startingAt:shutterOpenTime endingAt:shutterCloseTime]; RiMotionEnd(); } } } } else { // either we're going to the screen or this command isn't even motion-blurrable... [firstSampleData renderSelf:camera startingAt:shutterOpenTime endingAt:shutterCloseTime]; } } else { // okay, we're being asked to render a different (set of) sample(s) of myself // we'll refill the cache and then render // we now need to find the sample in the samplesList that had a // timestamp <= shutterOpenTime, where the next sample past it is < // shutterOpenTime, and then we need to find the sample that's // timestamp is <= shutterCloseTime where the next sample past it is > // shutterCloseTime. // probably the best thing to do is have samples be stored in a subclass of List // that caches the last answers to questions and searches itself accordingly... // In this case, it's a WWSampleList, which is built to store WWSamples firstSampleData = [(WWSample *)[samplesList sampleAtOrJustBefore:shutterOpenTime] data]; lastSampleData = [(WWSample *)[samplesList sampleAtOrJustBefore:shutterCloseTime] data]; if (!firstSampleData) { if (!lastSampleData) { NXLogError("couldn't get any valid samples for <%s> for times %f through %f\n", [cmd cmd], shutterOpenTime, shutterCloseTime); return nil; } else { NXLogError("couldn't get a valid sample for <%s> at open time %f - using sample at %f...\n", [cmd cmd], shutterOpenTime, shutterCloseTime); firstSampleData = lastSampleData; } } else { if (!lastSampleData) { NXLogError("couldn't get a valid sample for <%s> at close time %f - using sample at %f...\n", [(WWTCLClosedCmd *)cmd cmd], shutterCloseTime, shutterOpenTime); lastSampleData = firstSampleData; } } lastShutterOpenTime = shutterOpenTime; lastShutterCloseTime = shutterCloseTime; isMotionBlurrable = [firstSampleData isMotionBlurrable]; isCompoundCommand = [firstSampleData isCompoundCommand]; cacheIsFilled = YES; if (isMotionBlurrable && (NXDrawingStatus != NX_DRAWING)) { if (shutterOpenTime == shutterCloseTime) // doesn't matter; it's a strobe shot { // use first cached renderable command to be Renderman compliant [firstSampleData renderSelf:camera startingAt:shutterOpenTime endingAt:shutterCloseTime]; } else // same shot, but need to do it motion blurred! { if (firstSampleData && lastSampleData) { bogusTimeVector[0] = shutterOpenTime; bogusTimeVector[1] = shutterCloseTime; if (isCompoundCommand) // it's a compound command; need to walk down the two { [self renderCompoundCommand:firstSampleData :lastSampleData to:camera startingAt:shutterOpenTime endingAt:shutterCloseTime]; } else // it's an atomic RIBCommand... { // actually, I should ask the first command to // compare itself to the second, and only motion // blur it if it thinks they're different. // also, I could check at this point if ... what? I forgot... if ([firstSampleData theSameAs:lastSampleData] || (![firstSampleData similarTo:lastSampleData])) { // no need for motion blur; t'aint changing or is too weird... if (![firstSampleData isMoot]) // only render it if it will actually *do* something... { [firstSampleData renderSelf:camera startingAt:shutterOpenTime endingAt:shutterCloseTime]; } } else { RiMotionBeginV((RtInt)2, bogusTimeVector); [firstSampleData renderSelf:camera startingAt:shutterOpenTime endingAt:shutterCloseTime]; [lastSampleData renderSelf:camera startingAt:shutterOpenTime endingAt:shutterCloseTime]; RiMotionEnd(); } } } if (!firstSampleData) { NXLogError("couldn't get a valid first sample for <%s>\n", [(WWTCLClosedCmd *)cmd cmd]); if (lastSampleData) { [lastSampleData renderSelf:camera startingAt:shutterOpenTime endingAt:shutterCloseTime]; } } if (!lastSampleData) { NXLogError("couldn't get a valid first sample for <%s>\n", [(WWTCLClosedCmd *)cmd cmd]); if (firstSampleData) { [firstSampleData renderSelf:camera startingAt:shutterOpenTime endingAt:shutterCloseTime]; } } } // closes "else // same shot, but need to do it motion blurred!" } else { // either we're going to the screen or this command isn't even motion-blurrable... [firstSampleData renderSelf:camera startingAt:shutterOpenTime endingAt:shutterCloseTime]; } } return self; } - renderSelf:(WW3DCamera *)camera { RtFloat shutterOpenTime = [camera shutterOpenTime], shutterCloseTime = [camera shutterCloseTime]; return [self renderSelf:camera startingAt:shutterOpenTime endingAt:shutterCloseTime]; } - transformCTM:(WW3DAttributeState *)attributeState startingAt:(RtFloat)shutterOpenTime endingAt:(RtFloat)shutterCloseTime { [(id <WWRenderable>)firstSampleData transformCTM:attributeState startingAt:shutterOpenTime endingAt:shutterCloseTime]; return self; } - calculateBoundingBoxStartingAt:(RtFloat)shutterOpenTime endingAt:(RtFloat)shutterCloseTime { if ([self hasBoundingBox]) { // oy. This is actually more complex than I'm letting on here. // what I should do is get the sample at the start of the interval, // get the sample at the end of the interval, and then take the max // of their two bounding boxes. // for now, fake it if (firstSampleData) { [self setBoundingBox:[firstSampleData boundingBoxStartingAt:shutterOpenTime endingAt:shutterCloseTime]]; } } //dirtyBoundingBox = FALSE; // for now, we won't cache stuff; this will force it to ask the right RIBCommand directly.. return self; } - (RtBound *)boundingBoxStartingAt:(RtFloat)intervalStart endingAt:(RtFloat)intervalEnd { if (!dirtyBoundingBox) { // okay, this means we haven't been sent any messages that would have invalidated the boundingBox cache // now check intervals... if ((intervalStart == bbIntervalStart) && (intervalEnd == bbIntervalEnd)) { // cool! we can use the cached version!! return &boundingBox; } } // damn! have to recalculate it.. [self calculateBoundingBoxStartingAt:intervalStart endingAt:intervalEnd]; // don't forget to refill the cache bbIntervalStart = intervalStart; bbIntervalEnd = intervalEnd; dirtyBoundingBox = FALSE; // so what things might invalidate the cache? Basically if I get a // new sample that lies with the sample interval, the cache is // invalidated. return &boundingBox; } - (BOOL)isMotionBlurrable { return isMotionBlurrable; } - (BOOL)isCompoundCommand { return isCompoundCommand; } // WavesWorld archiving: // writeEve:(NXStream *)stream // writeScene:(NXStream *)stream - writeEve:(NXStream *)stream atTabLevel:(int)tab { int i; for (i = 0; i < tab; i++) { NXPrintf(stream, "\t"); } NXPrintf(stream, "animatable: {%s};", [(WWTCLClosedCmd *)cmd cmd]); return self; } - writeScene:(NXStream *)stream atTabLevel:(int)tab { int i, j, howMany = [samplesList count]; id aSample; const char *aSampleGeneratorName; for (i = 0; i < tab; i++) { NXPrintf(stream, "\t"); } NXPrintf(stream, "animatable: {%s} {", [(WWTCLClosedCmd *)cmd cmd]); NXPrintf(stream, "\\\n"); // this is so we can have a new line that tcl won't see... for (j = 0; j < howMany; j++) { aSample = [samplesList objectAt:j]; // let's not bother if this was auto-generated... // need a better way of checking this, dude. // note that an auto-generated sample has the wrong eveCode (why is that? just think about it...)... aSampleGeneratorName = [aSample generatorName]; // if the name is NULL, write it out, or if the name isn't "WWSampleList" write it out if (!aSampleGeneratorName || strcmp("WWSampleList", aSampleGeneratorName)) { for (i = 0; i < tab; i++) { NXPrintf(stream, "\t"); } for (i = 0; i < strlen("animatable: "); i++) { NXPrintf(stream, " "); } // okay, so how do we want this to look? // we have a timestamp for each sample, great // now, recall that a sample might be a single WWSample or // it could be a WWSampleComponentList // {$timestamp { {$owner1 {$compiledSample1}} } // {$timestamp { {$owner1 {$compiledSample1}} {$owner2 {$compiledSample1}} {$owner3 {$compiledSample3}} } } NXPrintf(stream, "{%f { ", [aSample timestamp]); //[aSample writeEve:stream atTabLevel:0]; // kinda gross, but we've already moved the cursor where we want... [aSample writeEve:stream atTabLevel:tab]; NXPrintf(stream, "}} "); NXPrintf(stream, "\\\n"); // this is so we can have a new line that tcl won't see... } } for (i = 0; i < tab; i++) { NXPrintf(stream, "\t"); } for (i = 0; i < strlen("animatable: "); i++) { NXPrintf(stream, " "); } NXPrintf(stream, "};"); return self; } - write3DTextScene:(NXStream *)stream atTabLevel:(int)tab index:(int)index time:(float)time until:(float)lastTime { int i, j, howMany = [samplesList count]; id aSample; for (i = 0; i < tab; i++) { NXPrintf(stream, "\t"); } NXPrintf(stream, "startShape (%s); ", [[[samplesList objectAt:0] class] name]); // need tab // need index (position in current list) NXPrintf(stream, "animatable: {Translate [expr { %d * $__text__(tabLength)}] [expr {$__text__(spacingFactor) * %d * $__text__(spacing) * $__text__(fontSize)}] 0 };\n", tab, index); NXPrintf(stream, " animatable: {WW3DText $__text__(fontName) $__text__(fontSize) {"); NXPrintf(stream, "{%s}", [(WWTCLClosedCmd *)cmd cmd]); NXPrintf(stream, "} left;}\n"); // okay, now put out my instances, pushed back in space // the above RIBCommands will have put the cursor in the right place, we now need to shove these guys back in Z, though for (j = 0; j < howMany; j++) { aSample = [samplesList objectAt:j]; // okay, so how do we want this to look? // we have a timestamp for each sample, great // now, recall that a sample might be a single WWSample or // it could be a WWSampleComponentList // {$owner1 {$compiledSample1} // {$owner1 {$compiledSample1}} {$owner2 {$compiledSample1}} {$owner3 {$compiledSample3}} NXPrintf(stream, " "); // if there are more samples in the list, we hand in the next's timestamp as the "last" time // otherwise we hand in this sample's timestamp again, so it knows it's the last if (j == (howMany - 1)) { [aSample write3DTextScene:stream atTabLevel:0 index:0 time:[aSample timestamp] until:[aSample timestamp]]; // we've already move the origin in X and Y } else { [aSample write3DTextScene:stream atTabLevel:0 index:0 time:[aSample timestamp] until:[[samplesList objectAt:(j+1)] timestamp]]; // we've already move the origin in X and Y } NXPrintf(stream, "\n"); } NXPrintf(stream, "endShape;\n"); return self; } - writeInventorAtTime:(float)currentTime to:(NXStream *)stream atTabLevel:(int)tab { int i; id <WWRenderable> data; data = [(WWSample *)[samplesList sampleAtOrJustBefore:currentTime] data]; if ([data isMoot]) { return self; } for (i = 0; i < tab; i++) { NXPrintf(stream, "\t"); } NXPrintf(stream, "# animatable: {%s}\n", [(WWTCLClosedCmd *)cmd cmd]); for (i = 0; i < tab; i++) { NXPrintf(stream, "\t"); } NXPrintf(stream, "# at time %f, this animatable expression evaluated to:\n", currentTime); [data writeInventorAtTime:currentTime to:stream atTabLevel:tab]; return self; } // NeXTSTEP archiving: #define typeVector "@@@ccc@@@c" #define typeValues &cmd, &eveParser, &interp, \ &hasBoundingBox, &isMotionBlurrable, &isTCLCommand, \ &myShape, &samplesList, &sceneClock, \ &isCompoundCommand - read:(NXTypedStream *)stream { int version; [super read:stream]; version = NXTypedStreamClassVersion(stream, "EveCommand"); if (version == 0) NXReadTypes(stream, "i", &version), version=1; if (version == 1) { NXReadTypes(stream, typeVector, typeValues); NXReadArray(stream, "f", 6, boundingBox); } if (version == 2) { NXReadTypes(stream, typeVector, typeValues); NXReadArray(stream, "f", 6, boundingBox); NXReadTypes(stream, "cc", &pushesCTM, &popsCTM); } return self; } - write:(NXTypedStream *)stream { [super write:stream]; NXWriteTypes(stream, typeVector, typeValues); NXWriteArray(stream, "f", 6, boundingBox); NXWriteTypes(stream, "cc", &pushesCTM, &popsCTM); return self; } - (BOOL)theSameAs:otherRIBCommand { // make the default NO... return NO; } - (BOOL)similarTo:otherRIBCommand { if ([self class] != [otherRIBCommand class]) { return NO; } return YES; } - (BOOL)isMoot { return NO; } - (BOOL)isMootStartingAt:(float)startTime endingAt:(float)endTime { if ([[(WWSample *)[samplesList sampleAtOrJustBefore:startTime] data] isMoot]) { return YES; } if ([[(WWSample *)[samplesList sampleAtOrJustBefore:endTime] data] isMoot]) { return YES; } return NO; } - (const char *)sampleName { return sampleName; } // boy, this is dumb... This is to get around the stupid warnings from the compiler - ask wave for details - class { return [super class]; } - command { return cmd; } @end